package util

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"syscall"
)

// FileMkDir 递归创建目录
func FileMkDir(dir string) (bool, error) {
	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
		return false, err
	}
	return true, nil
}

// FileExist 文件 或 目录是否存在
func FileExist(file string) bool {
	_, err := FileInfo(file)
	return err == nil || os.IsExist(err)
}

// FileIsDir 路径是否目录
func FileIsDir(file string) (bool, error) {
	if fi, err := FileInfo(file); err == nil || os.IsExist(err) {
		return fi.IsDir(), nil
	} else {
		return false, err
	}
}

// FileAppend 追加写文件
func FileAppend(file, content string) (bool, error) {
	return _fileWriteWithFlagAndMode(file, content, os.O_APPEND|os.O_WRONLY|os.O_CREATE)
}

// FileWrite 覆盖写文件
func FileWrite(file, content string) (bool, error) {
	return _fileWriteWithFlagAndMode(file, content, os.O_TRUNC|os.O_WRONLY|os.O_CREATE)
}

// FileRead 读取文件内容
func FileRead(file string) ([]byte, error) {
	return os.ReadFile(file)
}

// FileReadLines 逐行读取文件
func FileReadLines(file string) (lines []string, err error) {
	err = FileWalkLines(file, func(l string) error {
		lines = append(lines, l)
		return nil
	})
	return
}

// FileWalkLines 逐行分析文件
func FileWalkLines(file string, fn func(l string) error) (err error) {
	var f *os.File
	if f, err = os.Open(file); err != nil {
		return
	}
	defer f.Close()
	for sc := bufio.NewScanner(f); sc.Scan(); {
		if err = fn(StrTrim(sc.Text())); err != nil {
			return
		}
	}
	return
}

// FileInfo 获得文件信息
func FileInfo(file string) (fs.FileInfo, error) {
	return os.Stat(file)
}

// FileReadPart 读取文件的部分内容
func FileReadPart(file string, offset, length int64) ([]byte, error) {
	if offset < 0 || length <= 0 {
		return nil, fmt.Errorf("wrong offset or length")
	}
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	fi, err := FileInfo(file)
	if err != nil {
		return nil, err
	}
	if fi.Size() <= offset {
		return nil, fmt.Errorf("offset can not >= filesize")
	}
	if offset+length > fi.Size() {
		length = fi.Size() - offset
	}
	bf := make([]byte, length)
	_, err = f.ReadAt(bf, offset)
	if err != nil {
		return nil, err
	}
	return bf, nil
}

// FileReadDir 读取目录下所有文件
func FileReadDir(dir string) ([]string, error) {
	files, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}
	dir = strings.TrimRight(dir, "/")
	var ret []string
	for _, f := range files {
		ret = append(ret, dir+"/"+f.Name())
	}
	return ret, nil
}

// FileReadDirAll 递归读取目录下所有文件
func FileReadDirAll(dir string) ([]string, error) {
	files, err := FileReadDir(dir)
	if err != nil {
		return nil, err
	}
	var more []string
	for _, f := range files {
		if isDir, _ := FileIsDir(f); isDir {
			if ffs, _ := FileReadDirAll(f); ffs != nil {
				more = append(more, ffs...)
			}
		}
	}
	return append(files, more...), nil
}

// FileDel 删除文件或空目录
func FileDel(file string) (bool, error) {
	if err := os.Remove(file); err != nil {
		if errors.Is(err, syscall.Errno(2)) {
			return true, nil
		}
		return false, err
	}
	return true, nil
}

// FileDelAll 删除目录及包含的所有文件和子目录
func FileDelAll(dir string) (bool, error) {
	if err := os.RemoveAll(dir); err != nil {
		return false, err
	}
	return true, nil
}

// FileMd5 取文件 MD5值
func FileMd5(file string) (string, error) {
	f, err := os.Open(file)
	if err != nil {
		return "", err
	}
	defer f.Close()
	md5h := md5.New()
	_, err = io.Copy(md5h, f)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(md5h.Sum(nil)), nil
}

// FileContentType 获取文件类型
func FileContentType(file *os.File) (string, error) {
	buffer := make([]byte, 512)
	if _, err := file.Read(buffer); err != nil {
		return "", err
	}
	if _, err := file.Seek(0, 0); err != nil {
		return "", err
	}
	return http.DetectContentType(buffer), nil
}

// FileExtension 文件扩展名
func FileExtension(file string) string {
	return strings.ToLower(filepath.Ext(file))
}

// 写文件的执行函数
func _fileWriteWithFlagAndMode(file, content string, flag int) (bool, error) {
	if _, err := FileMkDir(filepath.Dir(file)); err != nil {
		return false, err
	}
	f, err := os.OpenFile(file, flag, os.ModePerm)
	if err != nil {
		return false, err
	}
	defer f.Close()
	w := bufio.NewWriter(f)
	_, err = w.WriteString(content)
	if err != nil {
		return false, err
	}
	err = w.Flush()
	if err != nil {
		return false, err
	}
	return true, nil
}
